# ResultSetRowTransformer.java

    package org.codefilarete.stalactite.sql.result;

    import java.io.Serializable;
    import java.sql.ResultSet;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    import java.util.function.Function;

    import org.danekja.java.util.function.serializable.SerializableFunction;
    import org.danekja.java.util.function.serializable.SerializableSupplier;
    import org.codefilarete.tool.bean.Factory;
    import org.codefilarete.stalactite.sql.statement.binder.ResultSetReader;

    import static org.codefilarete.tool.Nullable.nullable;

    /**
     * A class aimed at creating flat (no graph) beans from a {@link ResultSet} row.
     * Will read a main/root column that determines if a bean can be created from it (is null ?), then applies some more "bean filler" that are
     * defined with {@link ColumnConsumer}.
     * Instances of this class can be reused over multiple {@link ResultSet} (supposed to have same columns).
     * They can also be adapted to other {@link ResultSet}s that haven't the exact same column names by duplicating them with {@link #copyWithAliases(Function)}.
     * Moreover they can also be cloned to another type of bean which uses the same column names with {@link #copyFor(Class, SerializableFunction)}.
     *
     * @param <I> the type of the bean key (Input)
     * @param <C> the type of the bean
     *
     * @author Guillaume Mary
     */
    public class ResultSetRowTransformer<C, I> implements ResultSetTransformer<C, I>, ResultSetRowAssembler<C> {

    	private final BeanFactory<C> beanFactory;

    	private final Class<C> beanType;

    	private final Set<ColumnConsumer<C, Object>> consumers = new HashSet<>();

    	private final Map<BeanRelationFixer<C, Object>, ResultSetRowTransformer<Object, Object>> relations = new HashMap<>();

    	/**
    	 * Constructor focused on simple cases where beans are built only from one column key.
    	 * Prefer {@link #ResultSetRowTransformer(Class, ColumnReader, SerializableFunction)} for more general purpose cases (multiple columns key)
    	 *
    	 * @param columnName the name of the column that contains bean key
    	 * @param reader object to ease column reading, indicates column type
    	 * @param beanFactory the bean creator, bean key will be passed as argument. Not called if bean key is null (no instantiation needed)
    	 */
    	public ResultSetRowTransformer(Class<C> beanType, String columnName, ResultSetReader<I> reader, SerializableFunction<I, C> beanFactory) {
    		this(beanType, new IdentifierArgBeanFactory<>(new SingleColumnReader<>(columnName, reader), beanFactory));
    	}

    	/**
    	 * Constructor with main and mandatory arguments
    	 *
    	 * @param beanType type of built instances
    	 * @param reader object to ease column reading, indicates column type
    	 * @param beanFactory the bean creator, bean key will be passed as argument. Not called if bean key is null (no instantiation needed)
    	 */
    	public ResultSetRowTransformer(Class<C> beanType, ColumnReader<I> reader, SerializableFunction<I, C> beanFactory) {
    		this(beanType, new IdentifierArgBeanFactory<>(reader, beanFactory));
    	}

    	/**
    	 * Constructor with main and mandatory arguments.
    	 * With this constructor an instance per {@link ResultSet} row will be created since there's no mean to distinguish a row instance from another
    	 * because no key reader is given
    	 *
    	 * @param beanType type of built instances
    	 * @param beanFactory the bean creator
    	 */
    	public ResultSetRowTransformer(Class<C> beanType, SerializableSupplier<C> beanFactory) {
    		this(beanType, new NoIdentifierBeanFactory<>(beanFactory));
    	}

    	/**
    	 * For internal usage
    	 * @param beanType type of built instances
    	 * @param beanFactory bean factory to use as constructor
    	 */
    	ResultSetRowTransformer(Class<C> beanType, BeanFactory<C> beanFactory) {
    		this.beanType = beanType;
    		this.beanFactory = beanFactory;
    	}

    	public Class<C> getBeanType() {
    		return beanType;
    	}

    	public BeanFactory<C> getBeanFactory() {
    		return beanFactory;
    	}

    	/**
    	 * Gives {@link ColumnConsumer}s of this instances
    	 *
    	 * @return not null
    	 */
    	public Set<ColumnConsumer<C, Object>> getConsumers() {
    		return consumers;
    	}

    	public Map<BeanRelationFixer<C, Object>, ResultSetRowTransformer<Object, Object>> getRelations() {
    		return relations;
    	}

    	/**
    	 * Defines a complementary column that will be mapped on a bean property.
    	 * Null values will be passed to the consumer, hence the property mapper must be "null-value proof".
    	 *
    	 * @param columnConsumer the object that will do reading and mapping
    	 * @return this
    	 */
    	@Override
    	public <O> ResultSetRowTransformer<C, I> add(ColumnConsumer<C, O> columnConsumer) {
    		this.consumers.add((ColumnConsumer<C, Object>) columnConsumer);
    		return this;
    	}

    	@Override
    	public <K, V> ResultSetRowTransformer<C, I> add(BeanRelationFixer<C, V> combiner, ResultSetRowTransformer<V, K> relatedBeanCreator) {
    		this.relations.put((BeanRelationFixer) combiner, (ResultSetRowTransformer) relatedBeanCreator);
    		return this;
    	}

    	@Override
    	public <T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableFunction<I, T> beanFactory) {
    		if (!(this.beanFactory instanceof IdentifierArgBeanFactory)) {
    			throw new UnsupportedOperationException("This instance can only be cloned with an identifier-arg constructor because it was created with one");
    		}
    		ResultSetRowTransformer<T, I> result = new ResultSetRowTransformer<>(beanType, ((IdentifierArgBeanFactory) this.beanFactory).copyFor(beanFactory));
    		result.consumers.addAll((Set) this.consumers);
    		this.relations.forEach((consumer, transformer) -> {
    			result.relations.put((BeanRelationFixer) consumer, transformer.copyFor(transformer.beanType, ((SerializableFunction) transformer.getBeanFactory().getFactory())));
    		});
    		return result;
    	}

    	@Override
    	public <T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableSupplier<T> beanFactory) {
    		if (!(this.beanFactory instanceof NoIdentifierBeanFactory)) {
    			throw new UnsupportedOperationException("This instance can only be cloned with a no-arg constructor because it was created with one");
    		}
    		ResultSetRowTransformer<T, I> result = new ResultSetRowTransformer<>(beanType, ((NoIdentifierBeanFactory) this.beanFactory).copyFor(beanFactory));
    		result.consumers.addAll((Set) this.consumers);
    		this.relations.forEach((consumer, transformer) -> {
    				result.relations.put((BeanRelationFixer) consumer, transformer.copyFor(transformer.beanType, ((SerializableSupplier) transformer.getBeanFactory().getFactory())));
    		});
    		return result;
    	}

    	@Override
    	public ResultSetRowTransformer<C, I> copyWithAliases(Function<String, String> columnMapping) {
    		ResultSetRowTransformer<C, I> result = new ResultSetRowTransformer<>(this.beanType, this.beanFactory.copyWithAliases(columnMapping));
    		this.consumers.forEach(c -> result.add(c.copyWithAliases(columnMapping)));
    		this.relations.forEach((consumer, transformer) -> result.add(consumer, transformer.copyWithAliases(columnMapping)));
    		return result;
    	}

    	@Override	// for adhoc return type
    	public ResultSetRowTransformer<C, I> copyWithAliases(Map<String, String> columnMapping) {
    		// equivalent to super.copyWithAlias(..) but because interface default method can't be invoked we have to copy/paste its code ;(
    		return copyWithAliases(columnMapping::get);
    	}

    	/**
    	 * Converts the current {@link ResultSet} row into a bean.
    	 * Depending on implementation of factory, it may return a brand new instance or a cached one (if the bean key is already known for instance).
    	 * Consumers will be applied to instance returned by the factory, as a consequence if bean comes from a cache it will be completed again, this may
    	 * do some extra work in case of simple property.
    	 *
    	 * @param resultSet not null
    	 * @return an instance of T, newly created or not according to implementation
    	 */
    	@Override
    	public C transform(ResultSet resultSet) {
    		return nullable(this.beanFactory.createInstance(resultSet))
    				.invoke(b -> assemble(b, resultSet))
    				.get();
    	}

    	/**
    	 * Implementation that applies all {@link ColumnConsumer} to the given {@link ResultSet}
    	 *
    	 * @param rootBean the bean built for the row
    	 * @param input any {@link ResultSet} positioned at row that must be read
    	 */
    	@Override
    	public void assemble(C rootBean, ResultSet input) {
    		// we consume simple properties
    		for (ColumnConsumer<C, ?> consumer : consumers) {
    			consumer.assemble(rootBean, input);
    		}

    		// we set related beans
    		for (Entry<BeanRelationFixer<C, Object>, ResultSetRowTransformer<Object, Object>> entry : relations.entrySet()) {
    			Object relatedBean = entry.getValue().transform(input);
    			if (relatedBean != null) {	// null related bean is considered as no relation
    				entry.getKey().apply(rootBean, relatedBean);
    			}
    		}
    	}

    	/**
    	 * Definition of a factory focused on {@link ResultSetRowTransformer} usage
    	 * @param <C> created type
    	 */
    	public interface BeanFactory<C> extends Factory<ResultSet, C>, CopiableForAnotherQuery<C> {

    		Serializable getFactory();

    		@Override
    		BeanFactory<C> copyWithAliases(Function<String, String> columnMapping);
    	}

    	/**
    	 * Specialization of {@link BeanFactory} for no-arg constructor classes.
    	 *
    	 * @param <C> created type
    	 */
    	public static class NoIdentifierBeanFactory<C> implements BeanFactory<C> {

    		private final SerializableSupplier<C> factory;

    		public NoIdentifierBeanFactory(SerializableSupplier<C> factory) {
    			this.factory = factory;
    		}

    		public C createInstance() {
    			return factory.get();
    		}

    		public SerializableSupplier<C> getFactory() {
    			return factory;
    		}

    		@Override
    		public C createInstance(ResultSet input) {
    			return factory.get();
    		}

    		@Override
    		public BeanFactory<C> copyWithAliases(Function<String, String> columnMapping) {
    			return new NoIdentifierBeanFactory<>(factory);
    		}

    		/**
    		 * Used to copy this instance for a subclass of its bean type, required for inheritance implementation.
    		 * @param beanFactory subclass no-arg constructor
    		 * @param <T> subclass type
    		 * @return a new {@link BeanFactory} for a subclass bean which read same column as this instance
    		 */
    		public <T extends C> NoIdentifierBeanFactory<T> copyFor(SerializableSupplier<T> beanFactory) {
    			return new NoIdentifierBeanFactory<>(beanFactory);
    		}
    	}

    	/**
    	 * Specialization of {@link BeanFactory} for one-arg constructor classes
    	 *
    	 * @param <I> constructor input type (also column type)
    	 * @param <C> bean type
    	 */
    	public static class IdentifierArgBeanFactory<I, C> implements BeanFactory<C> {

    		/** {@link ResultSet} reader */
    		private final ColumnReader<I> reader;

    		/** one-arg bean constructor */
    		private final SerializableFunction<I, C> factory;

    		public IdentifierArgBeanFactory(ColumnReader<I> reader, SerializableFunction<I, C> factory) {
    			this.reader = reader;
    			this.factory = factory;
    		}

    		public SerializableFunction<I, C> getFactory() {
    			return factory;
    		}

    		@Override
    		public C createInstance(ResultSet resultSet) {
    			return nullable(readBeanKey(resultSet)).map(this::newInstance).get();
    		}

    		protected I readBeanKey(ResultSet resultSet) {
    			return reader.read(resultSet);
    		}

    		protected C newInstance(I beanKey) {
    			return factory.apply(beanKey);
    		}

    		@Override
    		public IdentifierArgBeanFactory<I, C> copyWithAliases(Function<String, String> columnMapping) {
    			return new IdentifierArgBeanFactory<>(reader.copyWithAliases(columnMapping), this.factory);
    		}

    		/**
    		 * Used to copy this instance for a subclass of its bean type, required for inheritance implementation.
    		 * @param beanFactory subclass one-arg constructor
    		 * @param <T> subclass type
    		 * @return a new {@link BeanFactory} for a subclass bean which read same column as this instance
    		 */
    		public <T extends C> IdentifierArgBeanFactory<I, T> copyFor(SerializableFunction<I, T> beanFactory) {
    			return new IdentifierArgBeanFactory<>(this.reader, beanFactory);
    		}
    	}
    }